﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading.Tasks;
using System.IO;

using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;

namespace DonetArxLib
{
    class TextProcess
    {
        private Stack<Matrix3d> _stackTrans;
        private List<TextExtents> _textExtents;
        private const string TEXT_EXTERN_LAYER = "TextExternLines";

        public TextProcess()
        {
            _stackTrans = new Stack<Matrix3d>();
            _stackTrans.Push(Matrix3d.Identity);
            _textExtents = new List<TextExtents>();
        }

        public bool ExplodeMText { get; set; } = false;

        public List<TextExtents> TextExtents => _textExtents;

        public void GatherAllTextInfo()
        {
            var db = HostApplicationServices.WorkingDatabase;
            using (var trans = db.TransactionManager.StartTransaction())
            {
                var blkRcd = trans.GetObject(db.CurrentSpaceId, OpenMode.ForRead) as BlockTableRecord;
                if (blkRcd == null)
                    return;

                foreach (var id in blkRcd)
                {
                    if (id.IsErased || !id.IsWellBehaved || id.IsNull || !id.IsValid)
                        continue;

                    var entity = UtilFunc.GetObject(trans, id, OpenMode.ForRead) as Entity;
                    if (entity == null)
                        continue;

                    var layer = UtilFunc.GetObject(trans, entity.LayerId, OpenMode.ForWrite) as LayerTableRecord;
                    if (layer == null) continue;
                    if (layer.IsOff || layer.IsFrozen) continue;

                    if (entity is DBText text)
                        GetEntityExtens(text);
                    else if (entity is Dimension dim)
                        GetTextInfoFromDimension(dim, trans);
                    else if (entity is MText mt)
                        GetTextInfoFromMtext(mt, trans);
                    else if (entity is MLeader ml)
                        GetTextInfoFromMLeader(ml, trans);
                    else if (entity is BlockReference br)
                        GetTextInfoFromBlockReference(br, trans);
                    else if (entity.GetRXClass().Name == "TDbText")
                        GetTextInfoFromTDbTextOrTDbMText(entity, trans);
                    else if (entity.GetRXClass().Name == "TDbMText")
                        GetTextInfoFromTDbTextOrTDbMText(entity, trans);
                    else if (entity.GetRXClass().Name == "TDbSymbMultiLeader")
                        GetTextInfoFromTDbSymbMultiLeader(entity, trans);
                }

                trans.Abort();
            }
        }

        public void GetAllViewPortTextInfo(string filePath)
        {
            if (!File.Exists(filePath)) return;

            List<Viewport> viewPorts = UtilFunc.GetViewportDataInCurrent();
            var content = File.ReadAllText(filePath);
            List<TextExtents> txtExtents = Newtonsoft.Json.JsonConvert.DeserializeObject<List<TextExtents>>(content);

            int count =viewPorts.Count;
            foreach (Viewport vp in viewPorts)
            {
                if (count > 1 && vp.Number == 1) continue;

                Point2d viewCenter = vp.ViewCenter;
                var viewTarget = vp.ViewTarget;
                Matrix3d mt = Matrix3d.AlignCoordinateSystem (Point3d.Origin, Vector3d.XAxis, Vector3d.YAxis, Vector3d.ZAxis,
                        viewTarget, vp.Ucs.Xaxis, vp.Ucs.Yaxis, vp.Ucs.Zaxis);

                var viewCenter3d = new Point3d(viewCenter.X, viewCenter.Y, 0);
                var modelcCenter = viewCenter3d.TransformBy(mt);
                var mtxScale = Matrix3d.Scaling(1 / vp.CustomScale, modelcCenter);
                Matrix3d mtxFromVpToModel = mt.PreMultiplyBy(mtxScale);

                var width = vp.Width;
                var height = vp.Height;
                var pLT = new Point3d(viewCenter3d.X - width / 2, viewCenter3d.Y + height / 2, 0).TransformBy(mtxFromVpToModel);
                var pRB = new Point3d(viewCenter3d.X + width / 2, viewCenter3d.Y - height / 2, 0).TransformBy(mtxFromVpToModel);

                foreach (TextExtents ext in txtExtents)
                {
                    Point3d minPt=new Point3d(ext.Min[0], ext.Min[1],0);
                    Point3d maxPt=new Point3d(ext.Max[0], ext.Max[1],0);
                    if (ContainsBox(minPt,maxPt, pLT, pRB)) continue;

                    var minVp = minPt.TransformBy(mtxFromVpToModel.Inverse());
                    var maxVp = maxPt.TransformBy(mtxFromVpToModel.Inverse());

                    double lbx = minVp.X - viewCenter.X;
                    double lby = minVp.Y - viewCenter.Y;
                    double rtx = maxVp.X - viewCenter.X;
                    double rty = maxVp.Y - viewCenter.Y;

                    var centerPt = vp.CenterPoint;
                    var plbLayout = new Point3d(centerPt.X + lbx, centerPt.Y + lby, 0);
                    var prtLayout = new Point3d(centerPt.X + rtx, centerPt.Y + rty, 0);

                    _textExtents.Add(new TextExtents
                    {
                        Text = ext.Text,
                        Min = new[] { plbLayout.X, plbLayout.Y },
                        Max = new[] { prtLayout.X, prtLayout.Y }
                    });
                }
            }
        }

        public string SaveToFile(bool onlyText = false, string fileName = "")
        {
            if (fileName.Length < 1)
                fileName = HostApplicationServices.WorkingDatabase.Filename + ".json";

            if (onlyText)
            {
                return JustSaveTextContentToFile(fileName);
            }
            else
            {
                var content = Newtonsoft.Json.JsonConvert.SerializeObject(_textExtents);
                if (content != null && content.Length > 0)
                    File.WriteAllText(fileName, content, Encoding.UTF8);
            }

            return fileName;
        }

        public void DrawAllTextExtern()
        {
            UtilFunc.EnsureLayerExist(HostApplicationServices.WorkingDatabase, TEXT_EXTERN_LAYER);
            _textExtents.ForEach(DrawTextExternInternal);
        }

        public void DrawTextExtern(TextExtents textExtents)
        {
            UtilFunc.EnsureLayerExist(HostApplicationServices.WorkingDatabase, TEXT_EXTERN_LAYER);
            DrawTextExternInternal(textExtents);
        }

        #region private

        private string JustSaveTextContentToFile(string fileName = "")
        {
            if (fileName.Length < 1)
                fileName = HostApplicationServices.WorkingDatabase.Filename + ".json";

            var lines = _textExtents.Where(t => !string.IsNullOrEmpty(t.Text)).Select(t => t.Text).ToArray();
            if (lines.Length > 0)
                File.WriteAllLines(fileName, lines);

            return fileName;
        }

        private void DrawTextExternInternal(TextExtents textExtents)
        {
            if (textExtents == null)
                return;

            var lt = new Point3d(textExtents.Min[0], textExtents.Max[1], 0);
            var lb = new Point3d(textExtents.Min[0], textExtents.Min[1], 0);
            var rb = new Point3d(textExtents.Max[0], textExtents.Min[1], 0);
            var rt = new Point3d(textExtents.Max[0], textExtents.Max[1], 0);

            UtilFunc.AddLineToCurrentSpace(lt, lb, "TextExternLines");
            UtilFunc.AddLineToCurrentSpace(lb, rb, "TextExternLines");
            UtilFunc.AddLineToCurrentSpace(rb, rt, "TextExternLines");
            UtilFunc.AddLineToCurrentSpace(rt, lt, "TextExternLines");
        }

        private TextExtents GetEntityExtens(Entity ent, bool drawExtent = false)
        {
            if (ent.IsErased)
                return null;

            Extents3d ext3d;
            try { ext3d = ent.GeometricExtents; } catch (Exception) { return null; }

            var lt = new Point3d(ext3d.MinPoint.X, ext3d.MaxPoint.Y, 0);
            var lb = new Point3d(ext3d.MinPoint.X, ext3d.MinPoint.Y, 0);
            var rb = new Point3d(ext3d.MaxPoint.X, ext3d.MinPoint.Y, 0);
            var rt = new Point3d(ext3d.MaxPoint.X, ext3d.MaxPoint.Y, 0);

            var mtx = _stackTrans.Peek();

            lt = lt.TransformBy(mtx);
            lb = lb.TransformBy(mtx);
            rb = rb.TransformBy(mtx);
            rt = rt.TransformBy(mtx);

            if (Math.Abs(lt.X - lb.X) > 0.0000001)
            {
                ext3d.Set(lt, lt);

                ext3d.AddPoint(lt);
                ext3d.AddPoint(lb);
                ext3d.AddPoint(rb);
                ext3d.AddPoint(rt);

                lt = new Point3d(ext3d.MinPoint.X, ext3d.MaxPoint.Y, 0);
                lb = new Point3d(ext3d.MinPoint.X, ext3d.MinPoint.Y, 0);
                rb = new Point3d(ext3d.MaxPoint.X, ext3d.MinPoint.Y, 0);
                rt = new Point3d(ext3d.MaxPoint.X, ext3d.MaxPoint.Y, 0);
            }

            TextExtents ext = null;
            if (ent is DBText text)
            {
                ext = new TextExtents
                {
                    Text = text.TextString,
                    Min = new[] { lb.X, lb.Y },
                    Max = new[] { rt.X, rt.Y }
                };
            }
            else if (ent is MText mtext)
            {
                ext = new TextExtents
                {
                    Text = mtext.Text,
                    Min = new[] { lb.X, lb.Y },
                    Max = new[] { rt.X, rt.Y }
                };
            }

            _textExtents.Add(ext);
            return ext;
        }

        private void GetTextInfoFromDimension(Dimension dim, Transaction trans)
        {
            var str = dim.DimensionText;
            DBObjectCollection dbc = new DBObjectCollection();
            try
            {
                dim.Explode(dbc);
                foreach (Entity ent in dbc)
                {
                    var layer = UtilFunc.GetObject(trans, ent.LayerId, OpenMode.ForWrite) as LayerTableRecord;
                    if (layer == null) continue;
                    if (layer.IsOff || layer.IsFrozen) continue;


                    if (ent is DBText text)
                        GetEntityExtens(text);
                    else if (ent is MText mt)
                        GetTextInfoFromMtext(mt, trans);
                }
            }
            catch (Exception)
            {
            }
        }

        private void GetTextInfoFromMtext(MText mtext, Transaction trans)
        {
            DBObjectCollection dbc = new DBObjectCollection();

            if (!ExplodeMText)
            {
                GetEntityExtens(mtext);
                return;
            }

            try
            {
                mtext.Explode(dbc);
                foreach (Entity ent in dbc)
                {
                    var layer = UtilFunc.GetObject(trans, ent.LayerId, OpenMode.ForWrite) as LayerTableRecord;
                    if (layer == null) continue;
                    if (layer.IsOff || layer.IsFrozen) continue;

                    if (ent is DBText text)
                        GetEntityExtens(text);
                    else if (ent is MText mt)
                        GetTextInfoFromMtext(mt, trans);
                }
            }
            catch (Exception)
            {
            }
        }

        private void GetTextInfoFromMLeader(MLeader mleader, Transaction trans)
        {
            DBObjectCollection dbc = new DBObjectCollection();

            try
            {
                mleader.Explode(dbc);
                foreach (Entity ent in dbc)
                {
                    var layer = UtilFunc.GetObject(trans, ent.LayerId, OpenMode.ForWrite) as LayerTableRecord;
                    if (layer == null) continue;
                    if (layer.IsOff || layer.IsFrozen) continue;

                    if (ent is DBText text)
                        GetEntityExtens(text);
                    else if (ent is MText mt)
                        GetTextInfoFromMtext(mt, trans);
                }
            }
            catch (Exception)
            {
            }
        }

        private void GetTextInfoFromBlockReference(BlockReference br, Transaction trans)
        {
            _stackTrans.Push(_stackTrans.Peek() * br.BlockTransform);

            var blkID = br.BlockTableRecord;    // 发现有的这个id是null
            if (blkID.IsErased || !blkID.IsWellBehaved || !blkID.IsValid || blkID.IsNull)
                return;

            var blk = trans.GetObject(br.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord;
            if (blk == null)
            {
                _stackTrans.Pop();
                return;
            }

            foreach (var entID in blk)
            {
                if (entID.IsErased || !entID.IsWellBehaved || !entID.IsValid || entID.IsNull)
                    continue;

                var ent = UtilFunc.GetObject(trans, entID, OpenMode.ForRead) as Entity;
                if (ent == null)
                    continue;

                var layer = UtilFunc.GetObject(trans, ent.LayerId, OpenMode.ForWrite) as LayerTableRecord;
                if (layer == null) continue;
                if (layer.IsOff || layer.IsFrozen) continue;

                if (ent is DBText txt)
                    GetEntityExtens(txt);
                else if (ent is Dimension dim)
                    GetTextInfoFromDimension(dim, trans);
                else if (ent is MText mt)
                    GetTextInfoFromMtext(mt, trans);
                else if (ent is MLeader ml)
                    GetTextInfoFromMLeader(ml, trans);
                else if (ent is BlockReference brf)
                    GetTextInfoFromBlockReference(brf, trans);
                else if (ent.GetRXClass().Name == "TDbText")
                    GetTextInfoFromTDbTextOrTDbMText(ent, trans);
                else if (ent.GetRXClass().Name == "TDbMText")
                    GetTextInfoFromTDbTextOrTDbMText(ent, trans);
                else if (ent.GetRXClass().Name == "TDbSymbMultiLeader")
                    GetTextInfoFromTDbSymbMultiLeader(ent, trans);
            }

            _stackTrans.Pop();
        }

        private void GetTextInfoFromTDbTextOrTDbMText(Entity ent, Transaction trans)
        {
            try
            {
                DBObjectCollection dbc = new DBObjectCollection();
                ent.Explode(dbc);
                foreach (DBObject dbo in dbc)
                {
                    var entTxt = dbo as Entity;
                    if (entTxt != null)
                    {
                        if (entTxt is DBText txt)
                        {
                            var layer = UtilFunc.GetObject(trans, ent.LayerId, OpenMode.ForWrite) as LayerTableRecord;
                            if (layer == null) continue;
                            if (layer.IsOff || layer.IsFrozen) continue;

                            GetEntityExtens(txt);
                        }
                    }
                }
            }
            catch (Exception)
            {
                return;
            }

        }

        private void GetTextInfoFromTDbSymbMultiLeader(Entity ent, Transaction trans)
        {
            try
            {
                DBObjectCollection dbc = new DBObjectCollection();
                ent.Explode(dbc);
                foreach (DBObject dbo in dbc)
                {
                    var entTd = dbo as Entity;
                    if (entTd != null)
                        if (entTd.GetRXClass().Name == "TDbText")
                            GetTextInfoFromTDbTextOrTDbMText(entTd, trans);
                }
            }
            catch (Exception)
            {
                return;
            }
        }

        private bool ContainsBox(Point3d min,Point3d max,Point3d tMin,Point3d tMax)
        {
            if (tMin.X > max.X || tMax.X < min.X ||
                tMin.Y > max.Y || tMax.Y < min.Y)
            {
                return false;
            }

            return true;
        }

        #endregion
    }

    class TextExtents
    {
        public string Text { get; set; }
        public double[] Min { get; set; }
        public double[] Max { get; set; }
    }
}
